#include <cstring>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>

void logStr(std::string_view str) { std::cout << str << std::endl; }

int main(int argc, char *argv[]) {
  if (argc < 3) {
    logStr("missing arg: file name or filtr");
    return 1;
  }

  const char *inFileName = argv[1];
  const char *filtrExpr = argv[2];
  std::cout << "inFileName: " << inFileName << std::endl;
  std::cout << "filtrExpr: " << filtrExpr << std::endl;

  //////////////
  // Read file
  //////////////

  auto loadFromFile = [inFileName]() {
    std::ifstream inFileStream(inFileName, std::ios_base::in);
    if (!inFileStream.is_open()) {
      std::cout << "failed to open " << inFileName << std::endl;
      std::cout << "current path" << std::filesystem::current_path() << std::endl;
      throw std::logic_error("failed to open");
    }

    std::vector<std::string> fileContent;
    fileContent.reserve(5'500'000);

    for (std::string line; std::getline(inFileStream, line);) {
      fileContent.emplace_back(std::move(line));
    }

    inFileStream.close();
    std::cout << std::setprecision(2) << "total: " << fileContent.size() << std::endl;

    return fileContent;
  };

  std::cout << "Loading from file: " << inFileName << " ... " << std::endl;
  const auto &fileContent = loadFromFile();
  std::cout << "Loading done" << std::endl;

  ///////////////////
  // getFiltr
  ////////////////////

  uint32_t totalCount = 0;

  using intsBt = std::pair<std::string_view, std::vector<std::string_view>>;
  auto filtrBTs = [&fileContent, &totalCount](std::string_view keyInst) {
    auto cmpBts = [](const intsBt &val) {
      auto hash = std::hash<std::string_view>{}(val.first);
      for (const auto &func : val.second) {
        hash ^= std::hash<std::string_view>{}(func);
      }

      return hash;
    };

    auto eqBts = [](const intsBt &l, const intsBt &r) {
      if (l.first != r.first || l.second != r.second)
        return false;

      return true;
    };

    std::unordered_map<intsBt, uint32_t, decltype(cmpBts), decltype(eqBts)> bts;
    constexpr const char *entryKV = "Entry: ";
    constexpr const char *exitKV = "Exit: ";
    auto lenEntryKV = strlen(entryKV);
    std::vector<std::string_view> bt;

    for (const auto &inst : fileContent) {
      std::string_view instView = inst;

      if (instView.starts_with(entryKV)) {
        bt.emplace_back(instView.substr(lenEntryKV));
      } else if (instView.starts_with(exitKV) && bt.size()) {
        bt.pop_back();
      }

      if (instView.ends_with(keyInst)) {
        ++totalCount;

        uint32_t chk = ++bts[std::make_pair(instView, bt)];
      }
    }

    return bts;
  };

  auto bts = filtrBTs(filtrExpr);

  ///////////////////
  // write_filtr
  ////////////////////

  auto writeFiltr = [&bts, inFileName, &totalCount]() {
    std::multimap<uint32_t, intsBt, std::greater<uint32_t>> sortedBts;

    for (const auto &[ibt, counter] : bts) {
      sortedBts.emplace(counter, std::move(ibt));
    }

    [[maybe_unused]] auto getDateTime = []() {
      auto t = std::time(nullptr);
      auto tm = *std::localtime(&t);

      std::ostringstream oss;
      oss << std::put_time(&tm, "%Y%m%d_%H%M%S");
      return oss.str();
    };

    std::string outFileName = inFileName;
    outFileName += "_";
    outFileName += getDateTime();
    outFileName += "_bts.csv";
    std::ofstream outFileStream(outFileName, std::ios_base::out | std::ios_base::trunc);
    outFileStream << "inst;Count;%;Backtrace" << std::endl;
    outFileStream << std::setprecision(2);

    for (const auto &[counter, ibt] : sortedBts) {

      outFileStream << ibt.first << ";";

      outFileStream << ";" << counter << ";" << static_cast<double>(counter) / static_cast<double>(totalCount) * 100.0
                    << ";";

      const auto &bt = ibt.second;
      for (auto it = bt.rbegin(), itEnd = bt.rend(); it != itEnd; ++it) {
        outFileStream << *it << ";";
      }

      outFileStream << std::endl;
    }

    outFileStream.close();

    std::cout << std::setprecision(2) << "Filtered total: " << totalCount << std::endl;
    std::cout << "Saved report to file: " << outFileName << std::endl;
  };

  writeFiltr();

  return 0;
}
